package org.thingsboard.server.schedule.core;

import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.SchedulerEvent;
import org.thingsboard.server.common.data.SchedulerEventInfo;
import org.thingsboard.server.common.data.SchedulerEventStatus;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.SchedulerEventId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.schedulerEvent.SchedulerEventService;
import org.thingsboard.server.schedule.SchedulerSystem;
import org.thingsboard.server.schedule.listener.CustomSchedulerListener;
import org.thingsboard.server.schedule.listener.ScheduleJobListener;
import org.thingsboard.server.schedule.listener.ScheduleTriggerListener;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.List;

@Service
@Slf4j
public class DefaultScheduleSystem implements SchedulerSystem {
    public static final String JOB_NAME = "JOB_NAME_";
    public static final String JOB_GROUP = "JOB_GROUP_";
    public static final String JOB_KEY = "TASK_KEY";
    @Autowired
    SchedulerEventService schedulerEventService;
    private SchedulerFactory schedulerFactory;
    private Scheduler scheduler;

    @PostConstruct
    public void initScheduleSystem() {
        schedulerFactory = new StdSchedulerFactory();
        JobListener customJobListener = new ScheduleJobListener();
        TriggerListener customTriggerListener = new ScheduleTriggerListener();
        CustomSchedulerListener customSchedulerListener = new CustomSchedulerListener();
        try {
            schedulerFactory.getScheduler().getListenerManager().addTriggerListener(customTriggerListener);
            schedulerFactory.getScheduler().getListenerManager().addJobListener(customJobListener);
            schedulerFactory.getScheduler().getListenerManager().addSchedulerListener(customSchedulerListener);
            scheduler = schedulerFactory.getScheduler();
        } catch (SchedulerException e) {
            log.error("quartz manager listener init error", e);
        }
    }


    /**
     * 上下文准备完毕
     * 读取数据库
     * 启动
     *
     * @param applicationReadyEvent
     */
    @EventListener(ApplicationReadyEvent.class)
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) throws SchedulerException, ThingsboardException {
        scheduler.start();
        List<SchedulerEventInfo> schedulerEventInfos =
                schedulerEventService.findSchedulerEventInfosByStatus(SchedulerEventStatus.RUNNING);
        if (!schedulerEventInfos.isEmpty()) {
            schedulerEventInfos.forEach(schedulerEventInfo -> initTask(schedulerEventInfo));
        }
        log.info("ScheduleSystem start and job is running ");
    }

    private void initTask(SchedulerEventInfo schedulerEventInfo) {
        addJob(getJobName(schedulerEventInfo.getId()),
                getJobGroupName(schedulerEventInfo.getTenantId()),
                schedulerEventInfo);
    }


    @Override
    public void onSchedulerEventChange(SchedulerEvent schedulerEvent, ActionType actionType) throws SchedulerException {
        onSchedulerEventInfoChange(new SchedulerEventInfo(schedulerEvent), actionType);
    }

    @Override
    public void onSchedulerEventInfoChange(SchedulerEventInfo schedulerEventInfo, ActionType actionType) throws SchedulerException {
        switch (actionType) {
            case DELETED:
                deleteJob(getJobName(schedulerEventInfo.getId()), getJobGroupName(schedulerEventInfo.getTenantId()));
                break;
            case ADDED:
                initTask(schedulerEventInfo);
                break;
            case UPDATED:
                updateTask(schedulerEventInfo);
                break;
        }
    }

    @Override
    public void triggerSchedulerEvent(SchedulerEventInfo schedulerEventInfo) {
        JobKey jobKey = getJobKey(schedulerEventInfo);
        try {
            runJobNow(jobKey, schedulerEventInfo);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * status 更新需要修改
     * cron   表达式修改需要更新
     * <p>
     * cron 修改
     * 状态修改    运行到暂停 /暂停到运行
     *
     * @param schedulerEventInfo
     */
    private void updateTask(SchedulerEventInfo schedulerEventInfo) throws SchedulerException {
        JobKey jobKey = getJobKey(schedulerEventInfo);
        if (scheduler.checkExists(jobKey)) {
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            SchedulerEventInfo old = (SchedulerEventInfo) jobDetail.getJobDataMap().get(JOB_KEY);
            if (schedulerEventInfo.getStatus() == SchedulerEventStatus.PAUSE) {
                deleteJob(getJobName(schedulerEventInfo.getId()), getJobGroupName(schedulerEventInfo.getTenantId()));
                return;
            }
            if (old.getSchedule().equals(schedulerEventInfo.getSchedule()))
                return;
            deleteJob(getJobName(schedulerEventInfo.getId()), getJobGroupName(schedulerEventInfo.getTenantId()));
        }
        initTask(schedulerEventInfo);
    }

    private JobKey getJobKey(SchedulerEventInfo schedulerEventInfo) {
        return new JobKey(getJobName(schedulerEventInfo.getId()), getJobGroupName(schedulerEventInfo.getTenantId()));
    }

    /**
     * 关闭所有
     */
    @PreDestroy
    public void shutdownJobs() {
        try {
            if (!scheduler.isShutdown()) {
                scheduler.shutdown();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private String getJobName(SchedulerEventId schedulerEventId) {
        return JOB_NAME + schedulerEventId.getId().toString();
    }

    private String getJobGroupName(TenantId tenantId) {
        return JOB_GROUP + tenantId.getId().toString();
    }

    public void deleteJob(String name, String group) throws SchedulerException {
        scheduler.deleteJob(new JobKey(name, group));
    }


    public void addJob(String jobName, String jobGroupName, SchedulerEventInfo schedulerEventInfo) {
        try {
            JobDataMap jobDataMap = new JobDataMap();
            jobDataMap.put(JOB_KEY, schedulerEventInfo);
            JobDetail jobDetail = JobBuilder.newJob(DefaultJobHandler.class)
                    .withIdentity(jobName, jobGroupName)
                    .usingJobData(jobDataMap)
                    .build();
            Trigger trigger = TriggerBuilder.newTrigger()
                    .startNow()
                    .withSchedule(CronScheduleBuilder.cronSchedule(schedulerEventInfo.getSchedule()))
                    .usingJobData(jobDataMap)
                    .build();
            scheduler.scheduleJob(jobDetail, trigger);
            if (!scheduler.isShutdown()) {
                scheduler.start();
            }
        } catch (Exception e) {
            log.warn("add Task failure[{}],is[{}]", schedulerEventInfo, e.getMessage());
        }
    }


    /**
     * 手动触发
     */
    public void runJobNow(JobKey jobKey, SchedulerEventInfo schedulerEventInfo) throws SchedulerException {
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put(JOB_KEY, schedulerEventInfo);
        jobDataMap.put("manual", "manual");
        scheduler.triggerJob(jobKey, jobDataMap);

    }


    public boolean clear() throws Exception {
        scheduler.clear();
        return true;
    }


}
